﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Data.OleDb;
using System.Data;
using System.Reflection;
using ModeloMagiPack.Entidades;

namespace DALMagiPack.Funcionalidad
{
    public class DALMagiPack<Entidad> where Entidad : EntidadMagiPack
    {
        private OleDbConnection _conn;
        private Type _tipo;

        public DALMagiPack()
        {
            _tipo = typeof(Entidad);
        }

        public OleDbConnection Connection 
        {
            get
            {
                if (_conn == null)
                {
                    string conn = ConfigurationSettings.AppSettings["AccessConfig"];

                    if (conn == null)
                        throw new DALMagiPackException("No se encontró el string de conexión AccessConfig");
                    else
                        _conn = new OleDbConnection(conn);
                }

                return _conn;
            }
            set { _conn = value; }
        }
        public OleDbTransaction Transaction { get; set; }


        #region Acceso a datos
        public void IniciarConexión()
        {
            if (Connection.State == ConnectionState.Closed && Transaction == null)
                Connection.Open();
        }

        public void CerrarConexión()
        {
            if (Connection.State == ConnectionState.Open && Transaction == null)
                Connection.Close();
        }

        public void IniciarTransacción()
        {
            Transaction = Connection.BeginTransaction();
        }

        public void ConfirmarTransacción()
        {
            Transaction.Commit();
            Transaction = null;
        }

        public void AnularTransacción()
        {
            Transaction.Rollback();
            Transaction = null;
        }

        public int ObtenerUlitmoID()
        {
            OleDbCommand selectIdentity = ObtenerInstanciaComando();
            int id = -1;

            selectIdentity.CommandText = "SELECT @@IDENTITY";
            IniciarConexión();
            id = (int) selectIdentity.ExecuteScalar();
            CerrarConexión();

            return id;
        }

        //Esté método sirve para colocar configuración general a todos los commands que se vayan generando.
        public OleDbCommand ObtenerInstanciaComando()
        {
            OleDbCommand comm = new OleDbCommand();
            comm.Connection = Connection;

            if (Transaction != null)
                comm.Transaction = Transaction;

            return comm;
        }

        #endregion

        #region ABM Genérico

        private void CargarParametros(OleDbCommand command, Entidad entidad)
        {
            foreach (PropertyInfo propiedad in _tipo.GetProperties())
            {
                OleDbParameter nuevoParametro = new OleDbParameter();
                nuevoParametro.ParameterName = "@" + propiedad.Name;
                nuevoParametro.Value = propiedad.GetValue(entidad, null);
                
                if (propiedad.PropertyType == typeof(int))
                    nuevoParametro.OleDbType = OleDbType.Integer;
                else if (propiedad.PropertyType.BaseType == typeof(Enum))
                {
                    nuevoParametro.OleDbType = OleDbType.Integer;
                    string[] nombres = Enum.GetNames(propiedad.PropertyType);

                    for (int i = 0; i < nombres.Length; i++)
                    {
                        if (nombres[i].Equals(nuevoParametro.Value.ToString()))
                        {
                            nuevoParametro.Value = i + 1;
                            break;
                        }
                    }
                }
                else if (propiedad.PropertyType == typeof(string))
                    nuevoParametro.OleDbType = OleDbType.VarChar;
                else if (propiedad.PropertyType == typeof(DateTime))
                    nuevoParametro.OleDbType = OleDbType.Date;

                command.Parameters.Add(nuevoParametro);
            }
        }
        
        public void Insertar(Entidad nuevaEntidad)
        {
            OleDbCommand insert = ObtenerInstanciaComando();
            string atributos, valores;
            int ultimoID;

            CargarParametros(insert, nuevaEntidad);
            atributos = valores = string.Empty;

            for (int i = 1; i < insert.Parameters.Count; i++)
            {
                atributos += insert.Parameters[i].ParameterName.Replace("@", "");

                if (insert.Parameters[i].OleDbType == OleDbType.Integer)
                    valores += insert.Parameters[i].Value;
                else if (insert.Parameters[i].OleDbType == OleDbType.Boolean)
                    valores += (bool.Parse(insert.Parameters[i].Value.ToString())) ? "1" : "0";
                else
                {
                    if (insert.Parameters[i].Value == null || insert.Parameters[i].Value.ToString().Length == 0)
                        valores += "NULL";
                    else
                        valores += "'" + insert.Parameters[i].Value + "'";
                }

                if (i < (insert.Parameters.Count - 1))
                {
                    atributos += ", ";
                    valores += ", ";
                }
            }

            insert.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES({2})", _tipo.Name, atributos, valores);

            IniciarConexión();
            insert.ExecuteNonQuery();
            ultimoID = ObtenerUlitmoID();
            _tipo.GetProperties().FirstOrDefault().SetValue(nuevaEntidad, ultimoID, null);
            CerrarConexión();
        }

        public void Modificar(Entidad entidad)
        {
            OleDbCommand update = ObtenerInstanciaComando();
            string valores = string.Empty;

            CargarParametros(update, entidad);

            for (int i = 1; i < update.Parameters.Count; i++)
            {
                valores += update.Parameters[i].ParameterName.Replace("@", "") + "=";

                if (update.Parameters[i].OleDbType == OleDbType.Integer)
                    valores += update.Parameters[i].Value;
                else if (update.Parameters[i].OleDbType == OleDbType.Boolean)
                    valores += (bool.Parse(update.Parameters[i].Value.ToString())) ? "1" : "0";
                else
                {
                    if (update.Parameters[i].Value == null || update.Parameters[i].Value.ToString().Length == 0)
                        valores += "NULL";
                    else
                        valores += "'" + update.Parameters[i].Value + "'";
                }

                if (i < (update.Parameters.Count - 1))
                    valores += ", ";
            }

            update.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2}={3}", _tipo.Name, valores, update.Parameters[0].ParameterName.Replace("@", ""), update.Parameters[0].ParameterName);
            IniciarConexión();
            bool exito = update.ExecuteNonQuery() > 0;
            CerrarConexión();

            if (!exito)
                throw new DALMagiPackException(string.Format("{0} {1} no ha sido encontrado", update.Parameters[0].ParameterName.Replace("@", ""), update.Parameters[0].Value.ToString()));
        }

        public void Eliminar(Entidad entidad)
        {
            OleDbCommand delete = ObtenerInstanciaComando();
            CargarParametros(delete, entidad);

            delete.CommandText = string.Format("DELETE FROM {0} WHERE {1}={2}", _tipo.Name, delete.Parameters[0].ParameterName.Replace("@", ""), delete.Parameters[0].ParameterName);
            IniciarConexión();
            bool exito = delete.ExecuteNonQuery() > 0;
            CerrarConexión();

            if (!exito)
                throw new DALMagiPackException(string.Format("{0} {1} no ha sido encontrado", delete.Parameters[0].ParameterName.Replace("@", ""), delete.Parameters[0].Value.ToString()));
        }

        public List<Entidad> Traer(Entidad filtro, List<Filtro> opciones)
        {
            List<Entidad> lista = new List<Entidad>();
            OleDbCommand select = ObtenerInstanciaComando();
            string columnas = string.Empty;
            string whereFinalFiltro = string.Empty;

            CargarParametros(select, filtro);

            for (int i = 0; i < select.Parameters.Count; i++)
            {
                string nombreParametro = select.Parameters[i].ParameterName;
                string nombreColumna = nombreParametro.Replace("@", "");
                columnas += nombreColumna + ", ";

                if (select.Parameters[i].Value != null)
                {
                    string valorParametro = select.Parameters[i].Value.ToString();
                    Filtro tipoFiltro = opciones.Where(t => t.Columna.ToString().Equals(nombreColumna)).FirstOrDefault();

                    if (tipoFiltro != null)
                    {
                        switch (tipoFiltro.Tipo)
                        {
                            case TipoFiltro.Equal:
                                if (select.Parameters[i].OleDbType == OleDbType.Integer)
                                    whereFinalFiltro += string.Format("({0}={1}) AND ", nombreColumna, valorParametro);
                                else if (select.Parameters[i].OleDbType == OleDbType.Boolean)
                                    whereFinalFiltro += string.Format("({0}={1}) AND ", nombreColumna, select.Parameters[i].Value.ToString());
                                else
                                    whereFinalFiltro += string.Format("({0}='{1}') AND ", nombreColumna, valorParametro);
                                break;
                            case TipoFiltro.Like:
                                whereFinalFiltro += string.Format("({0} LIKE '%{1}%') AND ", nombreColumna, valorParametro);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

            columnas = columnas.Remove(columnas.Length - 2);

            if (whereFinalFiltro.Length > 0)
            {
                whereFinalFiltro = whereFinalFiltro.Remove(whereFinalFiltro.Length - 4);
                select.CommandText = string.Format("SELECT {0} FROM {1} WHERE {2}", columnas, _tipo.Name, whereFinalFiltro);
            }
            else
                select.CommandText = string.Format("SELECT {0} FROM {1}", columnas, _tipo.Name);


            IniciarConexión();
            OleDbDataReader reader = select.ExecuteReader();

            while (reader.Read())
            {
                Entidad entidad = Activator.CreateInstance<Entidad>();

                foreach (PropertyInfo propiedad in _tipo.GetProperties())
                    if (!(reader[propiedad.Name] is DBNull))
                        propiedad.SetValue(entidad, reader[propiedad.Name], null);

                lista.Add(entidad);
            }

            CerrarConexión();

            return lista;
        }

        #endregion

        #region Operaciones varias

        public decimal RealizarCálculo(string consulta)
        {
            decimal resultado = 0;
            IniciarConexión();
            OleDbCommand cmd = ObtenerInstanciaComando();
            cmd.CommandText = consulta;
            resultado = (decimal) cmd.ExecuteScalar();
            CerrarConexión();

            return resultado;
        }

        public int Contar(Entidad filtro, List<Filtro> opciones)
        {
            OleDbCommand select = ObtenerInstanciaComando();
            string whereFinalFiltro = string.Empty;

            CargarParametros(select, filtro);

            for (int i = 0; i < select.Parameters.Count; i++)
            {
                string nombreParametro = select.Parameters[i].ParameterName;
                string nombreColumna = nombreParametro.Replace("@", "");

                if (select.Parameters[i].Value != null)
                {
                    string valorParametro = select.Parameters[i].Value.ToString();
                    Filtro tipoFiltro = opciones.Where(t => t.Columna.ToString().Equals(nombreColumna)).FirstOrDefault();

                    if (tipoFiltro != null)
                    {
                        switch (tipoFiltro.Tipo)
                        {
                            case TipoFiltro.Equal:
                                if (select.Parameters[i].OleDbType == OleDbType.Integer)
                                    whereFinalFiltro += string.Format("({0}={1}) AND ", nombreColumna, valorParametro);
                                else if (select.Parameters[i].OleDbType == OleDbType.Boolean)
                                    whereFinalFiltro += string.Format("({0}={1}) AND ", nombreColumna, select.Parameters[i].Value.ToString());
                                else
                                    whereFinalFiltro += string.Format("({0}='{1}') AND ", nombreColumna, valorParametro);
                                break;
                            case TipoFiltro.Like:
                                whereFinalFiltro += string.Format("({0} LIKE '%{1}%') AND ", nombreColumna, valorParametro);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

            if (whereFinalFiltro.Length > 0)
            {
                whereFinalFiltro = whereFinalFiltro.Remove(whereFinalFiltro.Length - 4);
                select.CommandText = string.Format("SELECT COUNT(*) FROM {0} WHERE {1}", _tipo.Name, whereFinalFiltro);
            }
            else
                select.CommandText = string.Format("SELECT COUNT(*) FROM {2}",  _tipo.Name);


            IniciarConexión();
            int cantidad = (int) select.ExecuteScalar();
            CerrarConexión();

            return cantidad;
        }

        #endregion
    }
}